package com.milotnt.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.milotnt.constants.RedisConstant;
import com.milotnt.mapper.TaskMapper;
import com.milotnt.pojo.ClassTable;
import com.milotnt.pojo.Task;
import com.milotnt.service.TaskService;
import com.milotnt.utils.ProtostuffUtil;
import com.milotnt.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Service
@Slf4j
public class TaskServiceImpl extends ServiceImpl<TaskMapper, Task> implements TaskService {
    @Override
    public void addTask(ClassTable table) {
        // 添加任务到数据库中
        Task task = addToDb(table);
        if(task != null){
            // 添加任务到redis
            addToRedis(task);
        }
    }

    @Override
    public void cancelTask(Integer classId) {
        // 删除数据库中的任务
        Task task = updateDb(classId);
        if(task != null){
            // redis重新从数据库中拉取最新的任务
            reloadData();
        }
    }

    @Override
    public Task pullTask() {
        Task task = null;
        String s = util.lRightPop(RedisConstant.TOPIC);
        if(StringUtils.isNotBlank(s)){
            task = JSON.parseObject(s, Task.class);
            // 在数据库中删除当前任务
            updateDb(task.getParameters());
        }
        return task;
    }

    private Task updateDb(Integer classId) {
        LambdaQueryWrapper<Task> lqw = new LambdaQueryWrapper<>();
        lqw.eq(Task::getParameters, classId);
        Task task = this.getOne(lqw);
        this.remove(lqw);
        return task;
    }

    @Autowired
    private RedisUtil util;

    // 添加任务到redis中
    private void addToRedis(Task task) {
        // 获取未来5分钟的时间
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);  // 添加5分钟
        long millis = calendar.getTimeInMillis();
        // 任务的执行时间小于等于当前时间，存入list中
        if(task.getExecuteTime().getTime() <= new Date().getTime()){
            util.lLeftPush(RedisConstant.TOPIC, JSON.toJSONString(task));
        } else if (task.getExecuteTime().getTime() <= millis) {
            // 任务的执行时间大于当前时间且小于预计的时间（未来5分钟），存入zset中
            util.zAdd(RedisConstant.FUTURE, JSON.toJSONString(task), task.getExecuteTime().getTime());
        }
    }

    // 添加任务到数据库中
    private Task addToDb(ClassTable table) {
        Task task = new Task();
        task.setParameters(table.getClassId());
        Date date = getTime(table.getClassBegin());
        task.setExecuteTime(date);
        boolean b = this.save(task);
        return b ? task : null;
    }

    // 将课程表中的开课时间转换为时间格式
    private Date getTime(String date){
        try {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
            return format.parse(date);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    // 定时刷新任务，每一分钟执行一次，将小于当前时间的数据存到list中
    @Scheduled(cron = "0 */1 * * * ?")
    public void fresh(){
        // 添加锁，防止为刷新完出现下一次的刷新
        String lock = util.tryLock("FUTURE_TASK_SYNC", 1000 * 30);// 设置存在时间为30s的锁
        if(StringUtils.isNotBlank(lock)){
            log.info("刷新任务");
            // 获取所有的未来任务
            Set<String> set = util.scan(RedisConstant.FUTURE);
            for (String s : set) {
                Set<String> tasks = util.zRangeByScore(s, 0, System.currentTimeMillis());
                // 同步数据
                if(!tasks.isEmpty()){
                    log.info("刷新一条数据");
                    util.refreshWithPipeline(s, RedisConstant.TOPIC, tasks);
                }
            }
        }
    }

    // 定时将数据库的任务同步到redis中，每五分钟执行一次
    @PostConstruct  // 在启动服务器之后会执行此方法
    @Scheduled(cron = "0 */5 * * * ?")
    public void reloadData(){
        // 清理redis中的list与zset中的任务
        clearData();
        // 查询符合条件的任务，时间小于未来5分钟
        Calendar calendar = Calendar.getInstance();
        calendar.add(Calendar.MINUTE, 5);  // 获取未来五分钟的时间
        LambdaQueryWrapper<Task> lqw = new LambdaQueryWrapper<>();
        lqw.lt(Task::getExecuteTime, calendar.getTime());
        List<Task> tasks = this.list(lqw);
        // 把任务添加到redis中
        if(tasks != null && tasks.size() > 0){
            for (Task task : tasks) {
                addToRedis(task);
            }
        }
        log.info("同步数据成功！");
    }

    // 清理redis中的任务，防止与数据库中的任务重复
    public void clearData(){
        // 获取所有任务
        Set<String> futureTask = util.scan(RedisConstant.FUTURE);
        Set<String> topicTask = util.scan(RedisConstant.TOPIC);
        util.delete(futureTask);
        util.delete(topicTask);
    }
}
